package com.avo.ramcache.service.impl;

import com.avo.ramcache.anno.core.Cached;
import com.avo.ramcache.anno.core.InitialConfig;
import com.avo.ramcache.anno.core.type.CacheType;
import com.avo.ramcache.config.CachedEntityConfig;
import com.avo.ramcache.config.IEntity;
import com.avo.ramcache.orm.Accessor;
import com.avo.ramcache.orm.Querier;
import com.avo.ramcache.persist.AbstractListener;
import com.avo.ramcache.persist.Element;
import com.avo.ramcache.persist.Persister;
import com.avo.ramcache.persist.timing.TimingPersister;
import com.avo.ramcache.service.*;
import com.avo.ramcache.model.ConcurrentHashSet;
import com.avo.ramcache.model.LruSoftHashMap;
import org.apache.commons.collections.bidimap.DualHashBidiMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

@SuppressWarnings({"unchecked"})
public class EntityCacheServiceImpl<PK extends Comparable<PK> & Serializable, T extends IEntity<PK>> implements EntityCacheService<PK, T>, CacheFinder<PK, T>, EntityEnhanceService<PK, T> {

    private static final Logger LOGGER = LoggerFactory.getLogger(EntityCacheServiceImpl.class);
    private boolean initialize;
    private Class<T> entityClz;
    private CachedEntityConfig config;
    private Accessor accessor;
    private Querier querier;
    private Persister persister;
    private LruSoftHashMap<PK, T> cache;
    private final ConcurrentHashMap<PK, ReentrantLock> locks = new ConcurrentHashMap<>();
    private final ConcurrentHashSet<PK> removing = new ConcurrentHashSet<>();
    private HashMap<String, DualHashBidiMap> uniques;

    public EntityCacheServiceImpl() {

    }

    public synchronized void initialize(CachedEntityConfig config, Persister persister, Accessor accessor, Querier querier) {
        if (this.initialize) {
            throw new RuntimeException("重复初始化");
        }
        this.initFields(config, persister, accessor, querier);
        this.initCache(config, querier);
        this.initialize = true;
    }

    private void initFields(CachedEntityConfig config, Persister persister, Accessor accessor, Querier querier) {
        Cached cached = config.getCached();
        this.config = config;
        this.accessor = accessor;
        this.querier = querier;
        this.persister = persister;
        this.entityClz = (Class<T>) config.getClz();
        this.persister.addListener(this.entityClz, new AbstractListener() {
            @Override
            protected void onRemoveSuccess(Serializable id) {
                removing.remove(id);
            }

            @Override
            protected void onRemoveError(Serializable id, RuntimeException e) {
                removing.remove(id);
            }

            @Override
            protected void onUpdateError(IEntity<?> entity, RuntimeException e) {
                if (e instanceof ConcurrentModificationException) {
                    persister.put(Element.saveOf(entity));
                }
            }

            @Override
            protected void onSaveError(Serializable id, IEntity<?> entity, RuntimeException e) {
                if (e instanceof ConcurrentModificationException) {
                    persister.put(Element.saveOf(entity));
                }
            }
        });
        if (config.hasUniqueField()) {
            this.uniques = config.buildUniqueCache();
        }
        CacheType cacheType = cached.type();
        this.cache = cacheType.createCache(cached.initialCapacity(), config.getCachedSize(), cached.concurrencyLevel());
    }

    private void initCache(CachedEntityConfig config, Querier querier) {
        InitialConfig initial = config.getInitialConfig();
        if (initial == null) {
            return;
        }
        throw new RuntimeException("不应当在初始化加载数据");
    }

    @Override
    public T load(PK id) {
        uninitializeThrowException();
        if (this.removing.contains(id)) {
            return null;
        }
        // lru
        T current = this.cache.get(id);
        if (current != null) {
            return current;
        }

        Lock lock = this.lockPkLock(id);
        try {
            if (removing.contains(id)) {
                return null;
            }
            current = cache.get(id);
            if (current != null) {
                return current;
            }
            Accessor accessor = getAccessor(id);
            current = accessor.load(entityClz, id);
            if (current != null) {
                if (config.hasUniqueField()) {
                    Map<String, Object> uniqueValues = config.getUniqueValues(current);
                    for (Map.Entry<String, Object> entry : uniqueValues.entrySet()) {
                        addUniqueValue(entry.getKey(), entry.getValue(), id);
                    }
                }
                // 数据加载回调
                current.deserialize();
                cache.put(id, current);
            }
            return current;
        } finally {
            releasePkLock(id, lock);
        }
    }

    private void releasePkLock(PK id, Lock lock) {
        lock.unlock();
        this.locks.remove(id);
    }

    private Lock lockPkLock(PK id) {
        ReentrantLock lock = this.locks.get(id);
        if (lock == null) {
            lock = new ReentrantLock();
            ReentrantLock prevLock = this.locks.put(id, lock);
            lock = prevLock != null ? prevLock : lock;
        }
        lock.lock();
        return lock;
    }

    @Override
    public T loadOrCreate(PK id, EntityBuilder<PK, T> builder) {
        this.uninitializeThrowException();
        T current;
        if (!this.removing.contains(id)) {
            current = this.cache.get(id);
            if (current != null) {
                return current;
            }
        }
        Lock lock = this.lockPkLock(id);
        try {
            current = cache.get(id);
            if (current != null) {
                return current;
            }
            current = getAccessor(id).load(this.entityClz, id);
            boolean flag = this.removing.contains(id);
            // 缓存中没有 | 准备被删除
            if (current == null || flag) {
                // 钩子
                current = builder.newInstance(id);
                if (current == null) {
                    throw new RuntimeException("创建主键为" + id + "的实体" + entityClz.getName() + "时返回null");
                }

                if (current.getId() == null) {
                    throw new RuntimeException("创建主键为" + id + "的实体id" + entityClz.getName() + "时返回null");
                }

                if (config.hasUniqueField()) {
                    // 检查唯一属性值是否合法
                    Map<String, Object> uniqueValues = config.getUniqueValues(current);
                    // 回滚标记
                    boolean rollback = false;
                    // 迭代每一个唯一属性域
                    ArrayList<Map.Entry<String, Object>> temp = new ArrayList<>(uniqueValues.size());

                    for (Map.Entry<String, Object> entry : uniqueValues.entrySet()) {
                        String uniqueName = entry.getKey();
                        Object uniqueValue = entry.getValue();
                        ReentrantReadWriteLock.WriteLock uniqueLock = config.getUniqueWriteLock(uniqueName);
                        uniqueLock.lock();
                        try {
                            // 检查缓存数据
                            DualHashBidiMap unique = uniques.get(uniqueName);
                            if (unique.containsKey(uniqueValue)) {
                                rollback = true;
                                break;
                            }
                            // 检查缓存数据
                            T checkEntity = querier.unique(entityClz, config.getUniqueQuery(uniqueName), uniqueValue);
                            if (checkEntity != null) {
                                rollback = true;
                                break;
                            }
                            @SuppressWarnings("unchecked")
                            PK prev = (PK) unique.put(uniqueValue, id);
                            if (prev != null && !id.equals(prev)) {
                                // log 实体的唯一键与原实体的唯一键不一致
                                LOGGER.error("实体[{}]的唯一键值[{}]异常：原键值[{}] 当前键值[{}]", entityClz.getName(),
                                        uniqueValue, prev, id);
                            }
                            temp.add(entry);
                        } finally {
                            uniqueLock.unlock();
                        }
                    }

                    if (rollback) {
                        // 数据回滚
                        for (Map.Entry<String, Object> entry : temp) {
                            removeUniqueValue(entry.getKey(), entry.getValue());
                        }
                        throw new RuntimeException("");
                    }
                }
                if (flag) {
                    removing.remove(id);
                }
                persister.put(Element.saveOf(current));
            } else {
                if (config.hasUniqueField()) {
                    // 添加唯一属性缓存信息
                    Map<String, Object> uniqueValues = config.getUniqueValues(current);
                    for (Map.Entry<String, Object> entry : uniqueValues.entrySet()) {
                        addUniqueValue(entry.getKey(), entry.getValue(), id);
                    }
                }
            }
            // 数据加载回调
            current.deserialize();
            cache.put(id, current);
            return current;
        } finally {
            this.releasePkLock(id, lock);
        }
    }

    @Override
    public void writeBack(PK id, T entity) {
        uninitializeThrowException();
        if (removing.contains(id)) {
            if (LOGGER.isWarnEnabled()) {
                LOGGER.warn("尝试更新处于待删除的实体[{}:{}] 操作将被忽略", entityClz.getSimpleName(), id);
            }
            return;
        }
        persister.put(Element.updateOf(entity));
    }

    @Override
    public T remove(PK id) {
        uninitializeThrowException();
        if (removing.contains(id)) {
            return null;
        }
        Lock lock = lockPkLock(id);
        try {
            if (removing.contains(id)) {
                return null;
            }
            removing.add(id);
            T current = cache.remove(id);
            // 清理关联的唯一值
            if (current != null && config.hasUniqueField()) {
                Map<String, Object> uniqueValues = config.getUniqueValues(current);
                for (Map.Entry<String, Object> entry : uniqueValues.entrySet()) {
                    removeUniqueValue(entry.getKey(), entry.getValue());
                }
            }
            persister.put(Element.removeOf(id, entityClz));
            return current;
        } finally {
            releasePkLock(id, lock);
        }
    }

    @Override
    public void clear(PK id) {
        T current = cache.get(id);
        if (current == null) {
            return;
        }
        Lock lock = lockPkLock(id);
        try {
            current = cache.remove(id);
            if (current != null && config.hasUniqueField()) {
                Map<String, Object> uniqueValues = config.getUniqueValues(current);
                for (Map.Entry<String, Object> entry : uniqueValues.entrySet()) {
                    removeUniqueValue(entry.getKey(), entry.getValue());
                }
            }
        } finally {
            releasePkLock(id, lock);
        }
    }

    @Override
    public T unique(String name, Objects value) {
        return M2SUnique(0, name, value);
    }

    @Override
    public T M2SUnique(int server, String name, Objects value) {
        uninitializeThrowException();
        if (!config.hasUniqueField()) {
            throw new RuntimeException("实体" + entityClz.getName() + "没有唯一属性域");
        }

        PK id;
        ReentrantReadWriteLock.ReadLock readLock = config.getUniqueReadLock(name);
        readLock.lock();
        try {
            DualHashBidiMap unique = uniques.get(name);
            id = (PK) unique.get(name);
        } finally {
            readLock.unlock();
        }
        if (id != null) {
            return load(id);
        }

        T current;
        if (server > 0) {
            current = querier.unique(server, entityClz, config.getUniqueQuery(name), value);
        } else {
            current = querier.unique(entityClz, config.getUniqueQuery(name), value);
        }

        id = current.getId();
        Lock lock = lockPkLock(id);

        try {
            if (removing.contains(id)) {
                return null;
            }
            T prev = cache.get(id);
            if (prev != null) {
                return prev;
            }

            if (config.hasUniqueField()) {
                Map<String, Object> uniqueValues = config.getUniqueValues(current);
                for (Map.Entry<String, Object> entry : uniqueValues.entrySet()) {
                    addUniqueValue(entry.getKey(), entry.getValue(), id);
                }
            }
            cache.put(id, current);
            return current;
        } finally {
            releasePkLock(id, lock);
        }
    }

    @Override
    public CacheFinder<PK, T> getFinder() {
        uninitializeThrowException();
        return this;
    }


    private Accessor getAccessor(PK id) {
        return this.accessor;
    }


    private void uninitializeThrowException() {
        if (!this.initialize) {
            throw new RuntimeException("未完成初始化");
        }
    }

    @Override
    public T create(PK id, EntityBuilder<PK, T> builder) {
        uninitializeThrowException();
        if (!removing.contains(id)) {
            T current = cache.get(id);
            if (current != null) {
                return current;
            }
        }
        T current;
        Lock lock = lockPkLock(id);

        try {
            current = cache.get(id);
            if (current != null) {
                return current;
            }
            boolean flag = removing.contains(id);
            current = builder.newInstance(id);
            if (current == null) {
                throw new RuntimeException("创建主键为" + id + "的实体" + entityClz.getName() + "时返回null");
            }
            if (current.getId() == null) {
                throw new RuntimeException("创建主键为" + id + "的实体" + entityClz.getName() + "时实体的主键值返回null");
            }

            if (config.hasUniqueField()) {
                Map<String, Object> uniqueValues = config.getUniqueValues(current);
                for (Map.Entry<String, Object> entry : uniqueValues.entrySet()) {
                    String uniqueName = entry.getKey();
                    Object uniqueValue = entry.getValue();
                    ReentrantReadWriteLock.WriteLock uniqueWriteLock = config.getUniqueWriteLock(uniqueName);
                    uniqueWriteLock.lock();

                    try {
                        DualHashBidiMap unique = uniques.get(uniqueName);
                        @SuppressWarnings("unchecked")
                        PK prev = (PK) unique.put(uniqueValue, id);
                        if (prev != null) {
                            LOGGER.error("实体[{}]的唯一键值[{}]异常:原主键[{}],当前主键[{}]", entityClz.getName(), uniqueValue, prev, id);
                        }
                    } finally {
                        uniqueWriteLock.lock();
                    }
                }
            }
            if (flag) {
                removing.remove(id);
            }
            persister.put(Element.saveOf(current));
            cache.put(id, current);
            return current;
        } finally {
            releasePkLock(id, lock);
        }
    }


    @Override
    public CachedEntityConfig getEntityConfig() {
        uninitializeThrowException();
        return config;
    }

    @Override
    public Persister getPersister() {
        return persister;
    }

    @Override
    public void stopServer(int server) {
        Collection<T> allEntity = cache.values();
        for (T t : allEntity) {
            int entServer = t.getServer();
            // 判断是不是该服的，不是就跳过
            if (entServer != server) {
                continue;
            }
            cache.remove(t.getId());
        }
        if (persister instanceof TimingPersister) {
            TimingPersister timingPersister = (TimingPersister) this.persister;
            timingPersister.saveAllNow();
        }
    }

    @Override
    public void addElement(T current) {
        persister.put(Element.saveOf(current));
        cache.put(current.getId(), current);
    }

    @Override
    public T removeAndFresh(PK id) {
        uninitializeThrowException();
        if (removing.contains(id)) {
            return null;
        }
        Lock lock = lockPkLock(id);
        try {
            T current = cache.remove(id);
            if (current != null && config.hasUniqueField()) {
                Map<String, Object> uniqueValues = config.getUniqueValues(current);
                for (Map.Entry<String, Object> entry : uniqueValues.entrySet()) {
                    removeUniqueValue(entry.getKey(), entry.getValue());
                }
            }
            getAccessor(id).remove(entityClz, id);
            return current;
        } finally {
            releasePkLock(id, lock);
        }
    }

    private void addUniqueValue(String name, Object value, PK id) {
        DualHashBidiMap unique = uniques.get(name);
        ReentrantReadWriteLock.WriteLock lock = config.getUniqueWriteLock(name);
        lock.lock();
        try {
            @SuppressWarnings("unchecked")
            PK prev = (PK) unique.put(value, id);
            if (prev != null && !id.equals(prev)) {
                LOGGER.error("实体[{}]的唯一键值[{}]异常:原主键[{}],当前主键[{}]", entityClz.getName(), value, prev, id);
            }
        } finally {
            lock.unlock();
        }
    }

    @Override
    public boolean hasUniqueValue(String name, Objects value) {
        DualHashBidiMap unique = uniques.get(name);
        if (config.getUniqueWriteLock(name).isHeldByCurrentThread()) {
            if (unique.containsKey(value)) {
                return true;
            } else {
                T current = this.querier.unique(entityClz, config.getUniqueQuery(name), value);
                return current != null;
            }
        } else {
            ReentrantReadWriteLock.WriteLock lock = config.getUniqueWriteLock(name);
            lock.lock();
            try {
                if (!unique.containsKey(value)) {
                    T current = this.querier.unique(entityClz, config.getUniqueQuery(name), value);
                    return current != null;
                }
                return true;
            } finally {
                lock.unlock();
            }
        }
    }

    @Override
    public void replaceUniqueValue(PK id, String name, Object value) {
        if (config.getUniqueWriteLock(name).isHeldByCurrentThread()) {
            throw new RuntimeException("非法执行该方法");
        } else {
            DualHashBidiMap unique = uniques.get(name);
            unique.removeValue(id);
            unique.put(value, id);
        }
    }

    private void removeUniqueValue(String name, Object value) {
        DualHashBidiMap unique = uniques.get(name);
        ReentrantReadWriteLock.WriteLock uniqueLock = config.getUniqueWriteLock(name);
        uniqueLock.lock();
        try {
            unique.remove(value);
        } finally {
            uniqueLock.unlock();
        }
    }

    @Override
    public Set<T> find(Filter<T> filter) {
        uninitializeThrowException();
        HashSet<T> result = new HashSet<>();
        for (T entity : cache.values()) {
            if (filter.isExclude(entity)) {
                continue;
            }
            result.add(entity);
        }
        return result;
    }

    @Override
    public List<T> sort(Comparator<T> comparator) {
        uninitializeThrowException();
        ArrayList<T> result = new ArrayList<>(cache.values());
        result.sort(comparator);
        return result;
    }

    @Override
    public List<T> find(Filter<T> filter, Comparator<T> comparator) {
        uninitializeThrowException();
        ArrayList<T> result = new ArrayList<>();
        for (T entity : cache.values()) {
            if (filter.isExclude(entity)) {
                continue;
            }
            result.add(entity);
        }
        result.sort(comparator);
        return result;
    }

    @Override
    public Set<T> all() {
        uninitializeThrowException();
        return new HashSet<>(cache.values());
    }

    @Override
    public int getAllSize() {
        uninitializeThrowException();
        return cache.getCacheMapSize();
    }
}
